package ru.usu.gv.gui.behavior;

import ru.usu.gv.core.GraphCanvas;
import ru.usu.gv.gui.layout.dynamic.DynamicLayoutUtilities;
import ru.usu.gv.styles.StyleFacade;
import ru.usu.gv.uicomponents.UIEdge;
import ru.usu.gv.uicomponents.UIGraph;
import ru.usu.gv.uicomponents.UIVertex;
import ru.usu.gv.utils.math.Matrix;
import ru.usu.gv.utils.math.mds.DistanceScaling;

import javax.media.j3d.BoundingSphere;
import javax.vecmath.Point3d;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author spupyrev
 * 06.11.2009
 */
public class OfflineDynamicVisBehavior extends DynamicVisBehavior
{
	public OfflineDynamicVisBehavior(BoundingSphere bound)
	{
		super(bound);
	}

	@Override
	public void processStimulus(Enumeration arg0)
	{
		wakeupOn(w);

		System.out.println("Running dynamic mds layout");

		//extract all graphs and layout them
		UIGraph currentGraph = GraphCanvas.getInstance().getCurrentGraph();
		int n = currentGraph.getVertexes().size();

		List<Map<Integer, Map<Integer, Double>>> graphs = extractSlicedGraph(currentGraph);

		System.out.println("slices: " + graphs.size());

		Matrix d = new Matrix(n + 1, n + 1);
		Matrix w = new Matrix(n + 1, n + 1);

		createMatrices(currentGraph, graphs, d, w);
		Matrix res = new DistanceScaling().mds(d, w, 2, false);
//		System.out.println("Debug RES:\n" + res);
		Map<Integer, Point3d> coordinates = buildCoordinates(currentGraph, graphs, res);

		setCoordinates(currentGraph, coordinates);
		currentGraph.draw();
		setEnable(false);
	}

	private Map<Integer, Map<Integer, Double>> floyd(Map<Integer, Map<Integer, Double>> edges)
	{
		Map<Integer, Integer> index = new HashMap();
		for (int id : edges.keySet())
			index.put(id, index.size());

		int n = edges.size();
		double[][] a = new double[n][n];
		double[][] bfs_a = new double[n][n];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
			{
				a[i][j] = (i == j ? 0 : 1e8);
				bfs_a[i][j] = (i == j ? 0 : 1e8);
			}

		for (int id : edges.keySet())
			for (int neig : edges.get(id).keySet())
			{
				double v = Math.pow(edges.get(id).get(neig), -0.3);
				a[index.get(id)][index.get(neig)] = a[index.get(neig)][index.get(id)] = 1;
				bfs_a[index.get(id)][index.get(neig)] = bfs_a[index.get(neig)][index.get(id)] = 1;
			}

		for (int k = 0; k < n; k++)
			for (int i = 0; i < n; i++)
				for (int j = 0; j < n; j++)
				{
					if (a[i][j] > a[i][k] + a[k][j])
						a[i][j] = a[i][k] + a[k][j];
					if (bfs_a[i][j] > bfs_a[i][k] + bfs_a[k][j])
						bfs_a[i][j] = bfs_a[i][k] + bfs_a[k][j];
				}

		Map<Integer, Map<Integer, Double>> res = new HashMap();
		for (int i : edges.keySet())
			for (int j : edges.keySet())
				if (i < j)
				{
					DynamicLayoutUtilities.addEdge(res, i, j, a[index.get(i)][index.get(j)]);
				}

		return res;
	}

	private double Z_SEP;

	private List<Map<Integer, Map<Integer, Double>>> extractSlicedGraph(UIGraph currentGraph)
	{
		List<Double> zz = new ArrayList<Double>();
		List<Map<Integer, Map<Integer, Double>>> graphs = new ArrayList();
		for (UIVertex v : currentGraph.getVertexes())
		{
			if (!zz.contains(v.getPosition().z))
			{
				zz.add(v.getPosition().z);
				graphs.add(new HashMap());
			}
		}

		Collections.sort(zz);
		Z_SEP = (zz.size() < 2 ? -1 : zz.get(1) - zz.get(0));
		for (UIVertex v : currentGraph.getVertexes())
		{
			int index = (int) ((v.getPosition().z - zz.get(0) + 1e-6) / Z_SEP);
			graphs.get(index).put(v.getVertex().getIndex(), new HashMap());
		}

		for (UIEdge e : currentGraph.getEdges())
		{
			UIVertex v = e.getV1();
			UIVertex u = e.getV2();
			if (v.getPosition().z != u.getPosition().z)
				continue;
			int index = (int) ((u.getPosition().z - zz.get(0) + 1e-6) / Z_SEP);
			DynamicLayoutUtilities.addEdge(graphs.get(index), v.getVertex().getIndex(), u.getVertex().getIndex(), 1.0);
		}

		List<Map<Integer, Map<Integer, Double>>> res = new ArrayList();
		for (int i = 0; i < graphs.size(); i++)
		{
			res.add(floyd(graphs.get(i)));
		}

		return res;
	}

	private void createMatrices(UIGraph currentGraph, List<Map<Integer, Map<Integer, Double>>> graphs, Matrix d, Matrix w)
	{
		int n = d.getRowCount() - 1;
		double REP = 0.2;
		double GRAV = 0.0002 * n;

		int nGraphs = graphs.size();
		for (int i = 0; i < nGraphs; i++)
		{
			Map<Integer, Map<Integer, Double>> graph = graphs.get(i);

			System.out.println("-------");
			for (int id : graph.keySet())
				for (int neig : graph.get(id).keySet())
				{
					double dist = graph.get(id).get(neig);
					//if (id == index1 || neig == index1)
					assert (dist > 0.0);
					System.out.println("dist (" + id + "-" + neig + "): " + dist);

					if (dist < 1e7)
					{
						//System.out.println("dist: " + dist);
						d.set(id, neig, dist * StyleFacade.getEdgeLength());
						w.set(id, neig, 1.0 / (dist * dist));
					}
					else
					{
						double v = 1e5;
						d.set(id, neig, v);
						w.set(id, neig, REP / v);
					}
				}

			//gravitation
			for (int id : graph.keySet())
			{
				d.set(id, n, 0);
				d.set(n, id, 0);
				w.set(id, n, GRAV);
				w.set(n, id, GRAV);
			}

		}

		//intermediate edges

		for (UIEdge e : currentGraph.getEdges())
		{
			UIVertex v = e.getV1();
			UIVertex u = e.getV2();
			if (v.getPosition().z == u.getPosition().z)
				continue;

			double delta = ((Math.abs(v.getPosition().z - u.getPosition().z) + 1e-6) / Z_SEP);

			d.set(v.getVertex().getIndex(), u.getVertex().getIndex(), 0);
			d.set(u.getVertex().getIndex(), v.getVertex().getIndex(), 0);

			double mentalStrength = 30.5;
			mentalStrength /= delta;// * (j - i);
			w.set(v.getVertex().getIndex(), u.getVertex().getIndex(), mentalStrength);
			w.set(u.getVertex().getIndex(), v.getVertex().getIndex(), mentalStrength);

			System.out.println("inter-edge: " + v.getVertex().getIndex() + "-" + u.getVertex().getIndex());
		}

		//System.out.println("Debug D:\n" + d);
		//System.out.println("Debug W:\n" + w);
	}

	private Map<Integer, Point3d> buildCoordinates(UIGraph currentGraph, List<Map<Integer, Map<Integer, Double>>> graphs, Matrix m)
	{
		Point3d p0 = m.getRowAsPoint(m.getRowCount() - 1);
		Map<Integer, Point3d> coordinates = new HashMap();
		for (int i = 0; i < graphs.size(); i++)
		{
			Map<Integer, Map<Integer, Double>> graph = graphs.get(i);
			for (int id : graph.keySet())
			{
				Point3d p = m.getRowAsPoint(id);
				p.sub(p0);
				p.z = currentGraph.getVertex(id).getPosition().z;
				coordinates.put(id, p);
			}
		}

		return coordinates;
	}

}
